#!/bin/bash
# This scipt lints go files for common errors.
#
# It runs gofmt and go vet, and optionally golint and
# gocyclo, if they are installed.
#
# With no arguments, it lints the current files staged
# for git commit.  Or you can pass it explicit filenames
# (or directories) and it will lint them.
#
# To use this script automatically, run:
#   ln -s ../../bin/lint .git/hooks/pre-commit

set -e

IGNORE_LINT_COMMENT=
IGNORE_TEST_PACKAGES=
IGNORE_SPELLINGS=
while true; do
	case "$1" in
		-nocomment)
		IGNORE_LINT_COMMENT=1
		shift 1
		;;
		-notestpackage)
		IGNORE_TEST_PACKAGES=1
		shift 1
		;;
        -ignorespelling)
        IGNORE_SPELLINGS="$2,$IGNORE_SPELLINGS"
        shift 2
        ;;
		*)
		break
	esac
done


function spell_check {
	filename="$1"
	local lint_result=0

	# we don't want to spell check tar balls, binaries, Makefile and json files
	if file "$filename" | grep executable >/dev/null 2>&1; then
		return $lint_result
	fi
	if [[ $filename == *".tar" || $filename == *".gz" || $filename == *".json" || $(basename "$filename") == "Makefile" ]]; then
		return $lint_result
	fi

	# misspell is completely optional.  If you don't like it
	# don't have it installed.
	if ! type misspell >/dev/null 2>&1; then
		return $lint_result
	fi

	if ! misspell -error -i "$IGNORE_SPELLINGS" "${filename}"; then
		lint_result=1
	fi

	return $lint_result
}

function test_mismatch {
	filename="$1"
	package=$(grep '^package ' "$filename" | awk '{print $2}')
	local lint_result=0

	if [[ $package == "main" ]]; then
		return # in package main, all bets are off
	fi

	if [[ $filename == *"_internal_test.go" ]]; then
		if [[ $package == *"_test" ]]; then
			lint_result=1
			echo "${filename}: should not be part of a _test package"
		fi
	else
		if [[ ! $package == *"_test" ]]; then
			lint_result=1
			echo "${filename}: should be part of a _test package"
		fi
	fi

	return $lint_result
}

function lint_go {
	filename="$1"
	local lint_result=0

	if [ -n "$(gofmt -s -l "${filename}")" ]; then
		lint_result=1
		echo "${filename}: run gofmt -s -w ${filename}!"
	fi

	go tool vet "${filename}" || lint_result=$?

	# golint is completely optional.  If you don't like it
	# don't have it installed.
	if type golint >/dev/null 2>&1; then
		# golint doesn't set an exit code it seems
		if [ -z "$IGNORE_LINT_COMMENT"  ]; then
			lintoutput=$(golint "${filename}")
		else
			lintoutput=$(golint "${filename}" | grep -vE 'comment|dot imports|ALL_CAPS')
		fi
		if [ -n "$lintoutput" ]; then
			lint_result=1
			echo "$lintoutput"
		fi
	fi

	# gocyclo is completely optional.  If you don't like it
	# don't have it installed.  Also never blocks a commit,
	# it just warns.
	if type gocyclo >/dev/null 2>&1; then
		gocyclo -over 25 "${filename}" | while read -r line; do
			echo "${filename}": higher than 25 cyclomatic complexity - "${line}"
		done
	fi

	return $lint_result
}

function lint {
	filename="$1"
	ext="${filename##*\.}"
	local lint_result=0

	# Don't lint deleted files
	if [ ! -f "$filename" ]; then
		return
	fi

	# Don't lint this script or static.go
	case "$(basename "${filename}")" in
	lint) return;;
	static.go) return;;
	coverage.html) return;;
	esac

	case "$ext" in
		go) lint_go "${filename}" || lint_result=1
		;;
	esac

	if [ -z "$IGNORE_TEST_PACKAGES" ]; then
		if [[ "$filename" == *"_test.go" ]]; then
			test_mismatch "${filename}" || lint_result=1
		fi
	fi

	spell_check "${filename}" || lint_result=1

	return $lint_result
}

function lint_files {
	local lint_result=0
	while read -r filename; do
		lint "${filename}" || lint_result=1
	done
	exit $lint_result
}

function list_files {
	if [ $# -gt 0 ]; then
        git ls-files --exclude-standard | grep -vE '(^|/)vendor/'
	else
		git diff --cached --name-only
	fi
}

list_files "$@" | lint_files
